#include "pbcxx.h"

// pair_class

pair_class::pair_class(const char* buf): is_preprocess(false) {
    int flag = pairing_init_set_str(pair, buf);
    assert(flag == 0);
}

pair_class::pair_class(const char* buf, size_t len): is_preprocess(false) {
    int flag = pairing_init_set_buf(pair, buf, len);
    assert(flag == 0);
}

pair_class::pair_class(const std::string buf): pair_class::pair_class(buf.c_str()) {}

pair_class::~pair_class() {
    if (is_preprocess) {
        pairing_pp_clear(pp);
    }
    pairing_clear(pair);
}

bool pair_class::is_symmetric() {
    return (1 == pairing_is_symmetric(pair));
}

void pair_class::preprocess(ele_class ele_c) {
    if (is_preprocess) {
        pairing_pp_clear(pp);
    } else {
        is_preprocess = true;
    }
    pairing_pp_init(pp, ele_c.ele, pair);
}

ele_class pair_class::operator()(ele_class lhs, ele_class rhs) {
    ele_class result(*this, "GT");
    pairing_apply(result.ele, lhs.ele, rhs.ele, pair);
    return result;
}

ele_class pair_class::operator()(ele_class rhs) {
    assert(is_preprocess);
    ele_class result(*this, "GT");
    pairing_pp_apply(result.ele, rhs.ele, pp);
    return result;
}

// ele_class

ele_class::ele_class(): is_pow_preprocess(false) {} // 一般不用

ele_class::ele_class(pair_class& pair, const std::string& group): is_pow_preprocess(false) {
    if (group == "G1") {
        element_init_G1(ele, pair.pair);
    } else if (group == "G2") {
        element_init_G2(ele, pair.pair);
    } else if (group == "GT") {
        element_init_GT(ele, pair.pair);
    } else if (group == "Zr") {
        element_init_Zr(ele, pair.pair);
    } else {
        assert(0);
    }
}

ele_class::ele_class(const ele_class& ele_c): is_pow_preprocess(false) {
    element_init_same_as(ele, const_cast<ele_class&>(ele_c).ele);
    element_set(ele, const_cast<element_s*>(ele_c.ele));
}

ele_class::ele_class(element_t ele_t): is_pow_preprocess(false) {
    element_init_same_as(ele, ele_t);
    element_set(ele, ele_t);
}

ele_class::~ele_class() {
    if (is_pow_preprocess) {
        element_pp_clear(ele_pp);
    }
    element_clear(ele);
}

ele_class& ele_class::operator=(const ele_class& ele_c) {
    ele_class& ele_c_1 = const_cast<ele_class&>(ele_c);
    PBC_ASSERT_MATCH2(ele, ele_c_1.ele);
    element_set(ele, ele_c_1.ele);
    return *this;
}

ele_class& ele_class::operator=(signed long int i) {
    element_set_si(ele, i);
    return *this;
}

ele_class& ele_class::operator=(mpz_class z) {
    element_set_mpz(ele, z.get_mpz_t());
    return *this;
}

void ele_class::set_str(const char* s, int base) {
    int code = element_set_str(ele, s, base);
    assert(code != 0);
}

void ele_class::set_str(std::string str, int base) {
    this->set_str(str.c_str(), base);
}

mpz_class ele_class::to_mpz_class() {
    mpz_class t;
    element_to_mpz(t.get_mpz_t(), ele);
    return t;
}

ele_class ele_class::operator+(const ele_class& ele_c) {
    element_t res;
    element_init_same_as(res, ele);
    element_add(res, ele, const_cast<ele_class&>(ele_c).ele);
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::operator+(signed long int z) {
    element_t res;
    element_init_same_as(res, ele);
    element_set_si(res, z);
    element_add(res, ele, res);
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::operator-(const ele_class& ele_c) {
    element_t res;
    element_init_same_as(res, ele);
    element_sub(res, ele, const_cast<ele_class&>(ele_c).ele);
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::operator*(const ele_class& ele_c) {
    element_t res;
    element_init_same_as(res, ele);
    element_mul(res, ele, const_cast<ele_class&>(ele_c).ele);
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::operator*(const mpz_class& z) {
    element_t res;
    element_init_same_as(res, ele);
    element_mul_mpz(res, ele, const_cast<mpz_class&>(z).get_mpz_t());
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::operator*(signed long int z) {
    element_t res;
    element_init_same_as(res, ele);
    if (z == 2) {
        element_double(res, ele);
    } else {
        element_mul_si(res, ele, z);
    }
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::operator/(const ele_class& ele_c) {
    element_t res;
    element_init_same_as(res, ele);   
    if (const_cast<ele_class&>(ele_c) == 2) {
        element_halve(res, ele);
    } else {
        element_div(res, ele, const_cast<ele_class&>(ele_c).ele);
    }
    
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::operator^(const ele_class& ele_c) {
    element_t res;
    element_init_same_as(res, ele);   
    if (const_cast<ele_class&>(ele_c) == 2) {
        element_square(res, const_cast<ele_class&>(ele_c).ele);
    } else if (is_pow_preprocess) {
        element_pp_pow_zn(res, const_cast<ele_class&>(ele_c).ele, ele_pp);
    } else {
        element_pow_zn(res, ele, const_cast<ele_class&>(ele_c).ele);
    }
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::operator^(const mpz_class& z) {
    element_t res;
    element_init_same_as(res, ele);   
    if (is_pow_preprocess) {
        element_pp_pow(res, const_cast<mpz_class&>(z).get_mpz_t(), ele_pp);
    } else {
        element_pow_mpz(res, ele, const_cast<mpz_class&>(z).get_mpz_t());
    }
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}

ele_class ele_class::inverse() {
    element_t res;
    element_init_same_as(res, ele);
    element_invert(res, ele);
    ele_class res_c(res);
    element_clear(res);
    return res_c;
}


bool ele_class::operator==(const ele_class& ele_c) const{
    return (0 == element_cmp(const_cast<element_s*>(ele), const_cast<ele_class&>(ele_c).ele));
}

bool ele_class::operator==(signed long int z) const {
    if (z == 0) {
        return element_is0(const_cast<element_s*>(ele));
    } else if (z == 1) {
        return element_is1(const_cast<element_s*>(ele));
    } else {
        element_t rhs;
        element_init_same_as(rhs, const_cast<element_s*>(ele));
        element_set_si(rhs, z);
        int flag = element_cmp(const_cast<element_s*>(ele), rhs);
        element_clear(rhs);
        return  (1 == flag);
    }
}

bool ele_class::operator!=(const ele_class& ele_c) const {
    return !(*this == ele_c);
}

bool ele_class::operator!=(signed long int z) const {
    return !(*this == z);
}

void ele_class::random() {
    element_random(ele);
}

void ele_class::pow_preprocess() {
    if (!is_pow_preprocess) {
        element_pp_init(ele_pp, ele);
        is_pow_preprocess = true;
    }
}

std::string ele_class::to_string() const {
    size_t BUFF_LEN = 2048;
    char* buff = new char[BUFF_LEN];
    ele_class* t = const_cast<ele_class*>(this);
    element_snprint(buff, BUFF_LEN, t->ele);
    std::string str(buff);
    delete[] buff;
    return str;
}

std::ostream& operator<<(std::ostream &os, const ele_class& ele_c) {
    os << ele_c.to_string();
    return os;
}

